#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "users.h"
#include "functions.h"
#define BUFLEN 256

int main(int argc, char** argv)
{
    FILE* input;
    int n, j, i, fdm, it_socket, jt_socket, numBytesRecived, numBytesSend;
    int tmp_ncard, tmp_pin, *block, *attemts, *session;
    double tmp_sum;
    int serverSocketTCP, clientSocketTCP, find;
    char buffer[BUFLEN], command[10];
    socklen_t sizeStructSocket;
    struct sockaddr_in serverSocketAddrTCP, clientSocketAddrTCP;
    fd_set read_fd, tmp_fd;
    struct User* database;
    if (argc != 3)
        serverUsage();
    input = fopen(argv[2], "r");
    fscanf(input, "%d", &n);
    block = (int*)malloc(n * sizeof(int));
    attemts = (int*)malloc(n * sizeof(int));
    session = (int*)malloc(n * sizeof(int));
    database = (struct User*)malloc(n * sizeof(struct User));

    for (i = 0; i < n; i++) {
        session[i] = 0;
        attemts[i] = 0;
        block[i] = 0;
        fscanf(input, "%s %s %d %d %s %le", database[i].nume, database[i].prenume,
               &database[i].ncard, &database[i].pin, database[i].parola, &database[i].sold);
    }

    FD_ZERO(&read_fd);
    FD_ZERO(&tmp_fd);

    //Deschidere socket TCP.
    serverSocketTCP = socket(PF_INET, SOCK_STREAM, 0);

    //Verificare deschidere socket.
    if (serverSocketTCP == -1) {
        perror("Socket error\n");
        return -1;
    }
    memset((char*)&serverSocketAddrTCP, 0, sizeof(serverSocketAddrTCP));

    //Configurare socket.
    serverSocketAddrTCP.sin_family = AF_INET;
    serverSocketAddrTCP.sin_port = htons(atoi(argv[1]));

    //Setare ip socket.
    inet_aton("127.0.0.1", &serverSocketAddrTCP.sin_addr);

    //Asociere socket <-> port.
    if ((bind(serverSocketTCP, (struct sockaddr*)&serverSocketAddrTCP, sizeof(struct sockaddr))) ==
        -1) {
        perror("Bind TCP error.\n");
        return -1;
    }

    //Setare socket pentru conectarea clientilor, maxim n clienti.
    if ((listen(serverSocketTCP, n)) == -1) {
        perror("Listen TCP error.\n");
        return -1;
    }
    //Adaugare socket raspunzator de conectarea clientilor in multimea descriptorilor de citire.
    FD_SET(serverSocketTCP, &read_fd);
    //Adaugare stdin pentru comanda "quit" in multimea descriptorilor de citire.
    FD_SET(0, &read_fd);
    fdm = serverSocketTCP;

    while (1) {
        tmp_fd = read_fd;
        if (select(fdm + 1, &tmp_fd, NULL, NULL, NULL) == -1) {
            perror("Select error.\n");
        }
        for (it_socket = 0; it_socket <= fdm; it_socket++) {
            if (FD_ISSET(it_socket, &tmp_fd)) {
                if (it_socket == serverSocketTCP) {
                    //Conexiune noua.
                    clientSocketTCP = accept(
                        serverSocketTCP, (struct sockaddr*)&clientSocketAddrTCP, &sizeStructSocket);
                    if (clientSocketTCP == -1) {
                        perror("Accept error.\n");
                    } else {
                        //Adaugare socket client nou in multimea descriptorilor de citire.
                        FD_SET(clientSocketTCP, &read_fd);
                        if (clientSocketTCP > fdm)
                            fdm = clientSocketTCP;
                    }
                } else {
                    if (it_socket == 0) {
                        //Comanda de la server.
                        scanf("%s", buffer);
                        if (strcmp(buffer, "quit") == 0) {
                            free(block);
                            free(attemts);
                            free(session);
                            free(database);
                            for (jt_socket = 0; jt_socket <= fdm; jt_socket++) {
                                if (jt_socket != 0 && jt_socket != serverSocketTCP &&
                                    FD_ISSET(jt_socket, &read_fd)) {
                                    send(jt_socket, buffer, 0, 0);
                                }
                            }
                            FD_ZERO(&read_fd);
                            FD_ZERO(&tmp_fd);
                            return 0;
                        }
                    } else {
                        //Primire mesaj din partea unui client.
                        memset(buffer, 0, BUFLEN);
                        numBytesRecived = recv(it_socket, buffer, sizeof(buffer), 0);
                        if (numBytesRecived <= 0) {
                            if (numBytesRecived < 0) {
                                perror("Error reciving message.\n");
                            }
                            close(it_socket);
                            FD_CLR(it_socket, &read_fd);
                        } else {
                            sscanf(buffer, "%s", &command);
                            if (strcmp(command, "login") == 0) {
                                find = 0;
                                sscanf(buffer, "%s %d %d", &command, &tmp_ncard, &tmp_pin);
                                for (i = 0; i < n; i++) {
                                    if (database[i].ncard == tmp_ncard) {
                                        find = 1;
                                        //Am gasit numarul de card pe care se incearca autentificarea.
                                        if (session[i] != 0) {
                                            //Sesiune deja deschisa.
                                            sprintf(buffer, "-2 : Sesiune deja deschisa \n");
                                            send(it_socket, buffer, strlen(buffer) + 1, 0);
                                            break;
                                        } else {
                                            //Sesiunea pentru cardul 'i' nu este deschisa.
                                            //Verific daca cardul este blocat.
                                            if (block[i] == 1) {
                                                sprintf(buffer, "-5 : Card blocat \n");
                                                send(it_socket, buffer, strlen(buffer) + 1, 0);
                                                break;
                                            } else {
                                                //Cardul nu este blocat, verific corectitudinea pin-ului.
                                                if (database[i].pin != tmp_pin) {
                                                    //Daca este incorect
                                                    if (attemts[i] == 2) {
                                                        //Blochez cardul.
                                                        attemts[i] = 3;
                                                        block[i] = 1;
                                                        sprintf(buffer, "-5 : Card blocat \n");
                                                        send(it_socket, buffer, strlen(buffer) + 1,
                                                             0);
                                                        break;
                                                    } else {
                                                        attemts[i]++;
                                                        sprintf(buffer, "-3 : Pin gresit \n");
                                                        send(it_socket, buffer, strlen(buffer) + 1,
                                                             0);
                                                        break;
                                                    }
                                                } else {
                                                    //Pin corect.
                                                    session[i] = it_socket;
                                                    sprintf(buffer, "Welcome %s %s \n",
                                                            database[i].nume, database[i].prenume,
                                                            i);
                                                    send(it_socket, buffer, strlen(buffer) + 1, 0);
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                                if (find == 0) {
                                    sprintf(buffer, "-4 : Numar card inexistent\n");
                                    send(it_socket, buffer, strlen(buffer) + 1, 0);
                                    break;
                                }
                            }
                            if (strcmp(command, "logout") == 0) {
                                for (i = 0; i < n; i++) {
                                    if (session[i] == it_socket) {
                                        //Facem deconectarea.
                                        session[i] = 0;
                                        sprintf(buffer, "Clientul a fost deconectat \n");
                                        send(it_socket, buffer, strlen(buffer) + 1, 0);
                                        break;
                                    }
                                }
                            }
                            if (strcmp(command, "quit") == 0) {
                                for (i = 0; i < n; i++) {
                                    if (session[i] == it_socket) {
                                        session[i] = 0;
                                        FD_CLR(it_socket, &read_fd);
                                    }
                                }
                            }
                            if (strcmp(command, "listsold") == 0) {
                                for (i = 0; i < n; i++) {
                                    if (session[i] == it_socket) {
                                        sprintf(buffer, "%.2f \n", database[i].sold, i,
                                                database[i].nume);
                                        send(it_socket, buffer, strlen(buffer) + 1, 0);
                                        break;
                                    }
                                }
                            }
                            if (strcmp(command, "transfer") == 0) {
                                find = 0;
                                sscanf(buffer, "%s %d %lf", command, &tmp_ncard, &tmp_sum);
                                for (i = 0; i < n; i++) {
                                    if (database[i].ncard == tmp_ncard) {
                                        find = 1;
                                        //Daca exista numarul de card introdus
                                        //verific daca am destui bani.
                                        for (j = 0; j < n; j++) {
                                            if (session[j] == it_socket) {
                                                if (database[j].sold >= tmp_sum) {
                                                    //Daca am destui bani in cont, trimit cerere confirmare.
                                                    sprintf(buffer,
                                                            "Transfer %.2lf catre %s %s ? [y/n] \n",
                                                            tmp_sum, database[i].nume,
                                                            database[i].prenume);
                                                    send(it_socket, buffer, strlen(buffer) + 1, 0);
                                                    break;
                                                } else {
                                                    sprintf(buffer, "-8 : Fonduri insuficiente \n");
                                                    send(it_socket, buffer, strlen(buffer) + 1, 0);
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                                if (find == 0) {
                                    sprintf(buffer, "-4 : Numar card inexistent \n");
                                    send(it_socket, buffer, strlen(buffer) + 1, 0);
                                    break;
                                }
                            }
                            //
                            if (strcmp(command, "transfer2") == 0) {
                                sscanf(buffer, "%s %d %lf", command, &tmp_ncard, &tmp_sum);
                                if (tmp_ncard != 0 && tmp_sum != 0) {
                                    for (i = 0; i < n; i++) {
                                        if (database[i].ncard == tmp_ncard) {
                                            for (j = 0; j < n; j++) {
                                                if (session[j] == it_socket) {
                                                    //j sursa, i destinatia
                                                    database[i].sold += tmp_sum;
                                                    database[j].sold -= tmp_sum;
                                                    sprintf(buffer,
                                                            "Transfer realizat cu succes \n");
                                                    send(it_socket, buffer, strlen(buffer) + 1, 0);
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                } else {
                                    sprintf(buffer, "-9 : Operatie anulata \n");
                                    send(it_socket, buffer, strlen(buffer) + 1, 0);
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return 0;
}
